package com.marketlive.system.keymanagement;

/*
 * (C) Copyright MarketLive. 2007. All rights reserved.
 * MarketLive is a trademark of MarketLive, Inc.
 * Warning: This computer program is protected by copyright law and international treaties.
 * Unauthorized reproduction or distribution of this program, or any portion of it, may result
 * in severe civil and criminal penalties, and will be prosecuted to the maximum extent
 * possible under the law.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

/**
 * Provides common utility functions for the KeyManagement package.
 */
public class Utilities {

    // Default hashing algorithm
    static final String HASH_TYPE = "SHA-256";

    // Default Random Number Generator
    static final String RNG_TYPE = "SHA1PRNG";

    // Default number of rounds for PBE
    static final short ITERATIONS = 1024;

    // Default RSA keysize
    static final short RSA_KEY_SIZE = 1024;

    // Default RSA encryption/decryption algorithm
    static final String RSA_CRYPTO_ALGORITHM="RSA/ECB/PKCS1Padding";

    // MarketLive environment
    private static final String MARKETLIVE_ENVIRONMENT_VARIABLE_NAME = "MARKETLIVE_ENVIRONMENT";
    private static final String MARKETLIVE_PRODUCTION_ENVIRONMENT_NAME = "production";

    /***************************************
     *  check PASSWORD (public method)
     ***************************************
     * Method checks whether the new password meets all the security requirments
     * @param   password
     * @return  String
     */
    public static String checkPassword(String password) {

        boolean valid = true;
        loop:   while (true) {

           // Check for null password
           if(password == null ) {

              try {
                  String prompt ="Password may not be null; Type in a password for the MLDB user: ";
                  char passwords[] = null;
                  passwords = getPassword(System.in, "\n" + prompt);
                  password = String.valueOf(passwords);
               } catch(IOException ioe) {
                  ioe.printStackTrace();
               }
              valid = false;
              continue loop;
           }

           // Check of short password
           String s = String.valueOf(password);
           if (s.length() < 6) {
               System.err.println("ERROR: Password must be at least 6 characters long");
               valid = false;
               break loop;
           }

           // Check for spaces
           if (s.contains(" ")) {
               System.err.println("ERROR: Password may not contain spaces");
               valid = false;
               break loop;
           }

           // Check for at least one numeral
           if (!s.contains("0") &
                   !s.contains("1") &
                   !s.contains("2") &
                   !s.contains("3") &
                   !s.contains("4") &
                   !s.contains("5") &
                   !s.contains("6") &
                   !s.contains("7") &
                   !s.contains("8") &
                   !s.contains("9")) {
               System.err.println("ERROR: Password must contain at least one numeral");
               valid = false;
               break loop;
           }

           // Check for at least one special character
           if (!s.contains("~") &
                   !s.contains("`") &
                   !s.contains("!") &
                   !s.contains("@") &
                   !s.contains("#") &
                   !s.contains("$") &
                   !s.contains("%") &
                   !s.contains("^") &
                   !s.contains("&") &
                   !s.contains("*") &
                   !s.contains("(") &
                   !s.contains(")") &
                   !s.contains("_") &
                   !s.contains("+") &
                   !s.contains("=") &
                   !s.contains("-") &
                   !s.contains("{") &
                   !s.contains("}") &
                   !s.contains("|") &
                   !s.contains("[") &
                   !s.contains("]") &
                   !s.contains("\\") &
                   !s.contains(":") &
                   !s.contains(";") &
                   // !s.contains("\"") &   Cannot use the double-quote (")
                   !s.contains("'") &
                   !s.contains("<") &
                   !s.contains(">") &
                   !s.contains("?") &
                   !s.contains(",") &
                   !s.contains(".") &
                   !s.contains("/")) {
               System.err.println("ERROR: Password must contain at least one special character");
               valid = false;
               break loop;
           }

           // Check for at least one alphabet character
           if (!s.contains("a") &
                   !s.contains("b") &
                   !s.contains("c") &
                   !s.contains("d") &
                   !s.contains("e") &
                   !s.contains("f") &
                   !s.contains("g") &
                   !s.contains("h") &
                   !s.contains("i") &
                   !s.contains("j") &
                   !s.contains("k") &
                   !s.contains("l") &
                   !s.contains("m") &
                   !s.contains("n") &
                   !s.contains("o") &
                   !s.contains("p") &
                   !s.contains("q") &
                   !s.contains("r") &
                   !s.contains("s") &
                   !s.contains("t") &
                   !s.contains("u") &
                   !s.contains("v") &
                   !s.contains("w") &
                   !s.contains("x") &
                   !s.contains("y") &
                   !s.contains("z") &
                   !s.contains("A") &
                   !s.contains("B") &
                   !s.contains("C") &
                   !s.contains("D") &
                   !s.contains("E") &
                   !s.contains("F") &
                   !s.contains("G") &
                   !s.contains("H") &
                   !s.contains("I") &
                   !s.contains("J") &
                   !s.contains("K") &
                   !s.contains("L") &
                   !s.contains("M") &
                   !s.contains("N") &
                   !s.contains("O") &
                   !s.contains("P") &
                   !s.contains("Q") &
                   !s.contains("R") &
                   !s.contains("S") &
                   !s.contains("T") &
                   !s.contains("U") &
                   !s.contains("V") &
                   !s.contains("W") &
                   !s.contains("X") &
                   !s.contains("Y") &
                   !s.contains("Z")) {
               System.err.println("ERROR: Password must contain at least one english alphabet character");
               valid = false;
               break loop;
           }
           valid = true;
           break loop;
      }
      //System.out.println("The password entered is: "+String.valueOf(password));
        if (valid){
            return String.valueOf(password);
        }else {
            return String.valueOf(valid);
        }
  }

    /*
    public static String getPassword(String prompt) {

        char password[] = null;

        loop:   while (true) {
           password = null;
           try {
              password = getPassword(System.in, "\n" + prompt);
           } catch(IOException ioe) {
              ioe.printStackTrace();
           }

           // Check for null password
           if(password == null ) {
              System.out.println("ERROR: Password may not be null");
              continue loop;
           }

           // Check of short password
           String s = String.valueOf(password);
           if (s.length() < 6) {
               System.err.println("ERROR: Password must be at least 6 characters long");
               continue loop;
           }

           // Check for spaces
           if (s.contains(" ")) {
               System.err.println("ERROR: Password may not contain spaces");
               continue loop;
           }

           // Check for at least one numeral
           if (!s.contains("0") &
                   !s.contains("1") &
                   !s.contains("2") &
                   !s.contains("3") &
                   !s.contains("4") &
                   !s.contains("5") &
                   !s.contains("6") &
                   !s.contains("7") &
                   !s.contains("8") &
                   !s.contains("9")) {
               System.err.println("ERROR: Password must contain at least one numeral");
               continue loop;
           }

           // Check for at least one special character
           if (!s.contains("~") &
                   !s.contains("`") &
                   !s.contains("!") &
                   !s.contains("@") &
                   !s.contains("#") &
                   !s.contains("$") &
                   !s.contains("%") &
                   !s.contains("^") &
                   !s.contains("&") &
                   !s.contains("*") &
                   !s.contains("(") &
                   !s.contains(")") &
                   !s.contains("_") &
                   !s.contains("+") &
                   !s.contains("=") &
                   !s.contains("-") &
                   !s.contains("{") &
                   !s.contains("}") &
                   !s.contains("|") &
                   !s.contains("[") &
                   !s.contains("]") &
                   !s.contains("\\") &
                   !s.contains(":") &
                   !s.contains(";") &
                   // !s.contains("\"") &   Cannot use the double-quote (")
                   !s.contains("'") &
                   !s.contains("<") &
                   !s.contains(">") &
                   !s.contains("?") &
                   !s.contains(",") &
                   !s.contains(".") &
                   !s.contains("/")) {
               System.err.println("ERROR: Password must contain at least one special character");
               continue loop;
           }

           // Check for at least one alphabet character
           if (!s.contains("a") &
                   !s.contains("b") &
                   !s.contains("c") &
                   !s.contains("d") &
                   !s.contains("e") &
                   !s.contains("f") &
                   !s.contains("g") &
                   !s.contains("h") &
                   !s.contains("i") &
                   !s.contains("j") &
                   !s.contains("k") &
                   !s.contains("l") &
                   !s.contains("m") &
                   !s.contains("n") &
                   !s.contains("o") &
                   !s.contains("p") &
                   !s.contains("q") &
                   !s.contains("r") &
                   !s.contains("s") &
                   !s.contains("t") &
                   !s.contains("u") &
                   !s.contains("v") &
                   !s.contains("w") &
                   !s.contains("x") &
                   !s.contains("y") &
                   !s.contains("z") &
                   !s.contains("A") &
                   !s.contains("B") &
                   !s.contains("C") &
                   !s.contains("D") &
                   !s.contains("E") &
                   !s.contains("F") &
                   !s.contains("G") &
                   !s.contains("H") &
                   !s.contains("I") &
                   !s.contains("J") &
                   !s.contains("K") &
                   !s.contains("L") &
                   !s.contains("M") &
                   !s.contains("N") &
                   !s.contains("O") &
                   !s.contains("P") &
                   !s.contains("Q") &
                   !s.contains("R") &
                   !s.contains("S") &
                   !s.contains("T") &
                   !s.contains("U") &
                   !s.contains("V") &
                   !s.contains("W") &
                   !s.contains("X") &
                   !s.contains("Y") &
                   !s.contains("Z")) {
               System.err.println("ERROR: Password must contain at least one english alphabet character");
               continue loop;
           }
           break loop;
      }
      //System.out.println("The password entered is: "+String.valueOf(password));
        return String.valueOf(password);
  }*/


  /**
   ****************************************
   *  GET PASSWORD (private method)
   ****************************************
   * Method accepts input characters from the input stream and
   * masks the input with asterisks.
   *
   * @param in  stream to be used (e.g. System.in)
   * @param prompt The prompt to display to the user.
   * @exception java.io.IOException if there is a problem with the stream
   * @return The password as entered by the user.
   */

   private static char[] getPassword(InputStream in, String prompt) throws IOException {
      MaskingThread maskingthread = new MaskingThread(prompt);
      Thread thread = new Thread(maskingthread);
      thread.start();

      char[] lineBuffer;
      char[] buf;

      buf = lineBuffer = new char[128];

      int room = buf.length;
      int offset = 0;
      int c;

      loop:   while (true) {
         switch (c = in.read()) {
            case -1:
            case '\n':
               break loop;

            case '\r':
               int c2 = in.read();
               if ((c2 != '\n') && (c2 != -1)) {
                  if (!(in instanceof PushbackInputStream)) {
                     in = new PushbackInputStream(in);
                  }
                  ((PushbackInputStream)in).unread(c2);
                } else {
                  break loop;
                }

                default:
                   if (--room < 0) {
                      buf = new char[offset + 128];
                      room = buf.length - offset - 1;
                      System.arraycopy(lineBuffer, 0, buf, 0, offset);
                      Arrays.fill(lineBuffer, ' ');
                      lineBuffer = buf;
                   }
                   buf[offset++] = (char) c;
                   break;
         }
      }
      maskingthread.stopMasking();
      if (offset == 0) {
         return null;
      }
      char[] ret = new char[offset];
      System.arraycopy(buf, 0, ret, 0, offset);
      Arrays.fill(buf, ' ');
      return ret;
   }

   /**
    *****************************************
    *  GENERATE DB PASSWORD
    *****************************************
    * Prompts the user for a password string, which is hashed to
    * a SHA-256 hash.  The hash is written out to a db.properties
    * file that must exist in the "location" parameter. Note that
    * the db.properties file must NOT have the "password" property
    * defined in it, otherwise this method will do nothing and exit.
    *
    * @param    dbplocation Directory where a db.properties file
    * exists (without the "password" property in it);
    *
    * @return boolean true if the method succeeded, or false otherwise
    *
    * @exception InvalidLocationException if location is zero-length
    * @exception EncryptedPasswordExistsException if "encryptedPassword"
    *       property exists in the db.properties file
    * @exception NonexistentJDBCParameterException if the "password" property
    *       DOES NOT exist in the db.properties file
    * @exception InvalidEnvironmentException if this is not PRODUCTION
    * @exception OtherKeyMgtException for all other errors
    */
   public static boolean generateDBPassword(String dbplocation, String password)
       throws InvalidLocationException, EncryptedPasswordExistsException,
       NonexistentJDBCParameterException, InvalidEnvironmentException,
       OtherKeyMgtException {

       // Is this production?
       String environment = System.getProperty(MARKETLIVE_ENVIRONMENT_VARIABLE_NAME, "null");
       if (environment.equals(MARKETLIVE_PRODUCTION_ENVIRONMENT_NAME))
           throw new InvalidEnvironmentException("Cannot change a PRODUCTION environment password.");

       // Is location zero-length?
       if (dbplocation.length() == 0)
           throw new InvalidLocationException("db.properties location cannot be zero-length");

       // Read in db.properties
       Properties props = new Properties();
       String jdbcDriver= null;
       String jdbcURL= null;
       String jdbcUsername = null;
       String jdbcPassword= null;

       try {
           props.load(new FileInputStream(dbplocation));

           // Check if encryptedPassword property is in there
           if (props.containsKey("encryptedPassword"))
               throw new EncryptedPasswordExistsException("db.properties already has the \"encryptedPassword\" property in it");

           // Check if driver property is NOT in there
           if (!props.containsKey("driver"))
               throw new NonexistentJDBCParameterException("db.properties does not contain the \"driver\" property in it");

           jdbcDriver = props.getProperty("driver");

           // Check if URL property is NOT in there
           if (!props.containsKey("url"))
               throw new NonexistentJDBCParameterException("db.properties does not contain the \"url\" property in it");

           jdbcURL = props.getProperty("url");

           // Check if username property is NOT in there
           if (!props.containsKey("username"))
               throw new NonexistentJDBCParameterException("db.properties does not contain the \"username\" property in it");

           jdbcUsername= props.getProperty("username");

           // Check if password property is NOT in there
           if (!props.containsKey("password"))
               throw new NonexistentJDBCParameterException("db.properties does not contain the \"password\" property in it");

           jdbcPassword = props.getProperty("password");


           // Get new password string
           //String passwordString = Utilities.getPassword("Type in a password for the MLDB user: ");

           // Retrive the password string
           String  passwordString= Utilities.checkPassword(password);

           if (passwordString.equalsIgnoreCase("false")){
               System.err.println("ERROR: The provided password is not valid. ");
               return false;
           }

           CryptoManager cm = new CryptoManager();

           // Add random salt to the password string
           SecureRandom secrand = SecureRandom.getInstance(RNG_TYPE);
           byte[] srbytes = new byte[16];
           secrand.nextBytes(srbytes);
           String salt = new String(srbytes);
           String saltedPasswordString = passwordString.concat(salt);

           // Hash password string and set property
           String sourceDigest = cm.hash(saltedPasswordString.getBytes("utf-8"), HASH_TYPE);
           props.setProperty("encryptedPassword", sourceDigest);

           // Hash the result and use it as DB password
           String newpassword = cm.hash(sourceDigest.getBytes("utf-8"), HASH_TYPE);

           // Debug stuff
           // props.setProperty("passwordString", passwordString);
           // props.setProperty("actualDBPassword", newpassword);

           // Connect to DB and change password
           if (!changeDBPassword(jdbcDriver, jdbcURL, jdbcUsername,
                   jdbcPassword, newpassword))
               throw new OtherKeyMgtException("Could not change password on the database server; check logs");

           // Delete the old password property
           props.remove("password");

           // If successful, write out new db.properties
           props.store(new FileOutputStream(dbplocation), null);

       } catch (IOException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An IOException was thrown; check logs");
       } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NoSuchAlgorithmException was thrown; check logss");
       }
       return true;
   }

   /**
    *****************************************
    *  CHANGE DB PASSWORD
    *****************************************
    * Uses JDBC to connect to the database, and changes the password
    * of the MLDB user - i.e. the user that owns the schema in which
    * all ML5 application data resides.
    *
    * @param    driver The JDBC driver from db.properties
    * @param    url The JDBC URL from db.properties
    * @param    username The JDBC username from db.properties
    * @param    oldpassword The JDBC password from db.properties
    * @param    newpassword The new encryptedPassword property value
    *
    * @return boolean true if the method succeeded, or false otherwise
    */
   private static boolean changeDBPassword(String driver, String url, String username,
           String oldpassword, String newpassword) {

       try {

           Class.forName(driver);
           Connection con = DriverManager.getConnection(url, username, oldpassword);
           Statement stmt = con.createStatement();
           if (url.contains("SQLServer")) {
               if (stmt.execute("ALTER LOGIN " + username + " WITH PASSWORD = '" + newpassword + "'"))
                   return false;  // SQLServer does not return a ResultSet
           } else { // Oracle
               /*
                * Oracle has a lot of constraints on passwords:
                *  - it cannot be greater than 30 chars in length
                *  - it must not begin with a number
                *  - it cannot contain quotation marks
                *  - it can contain only underscores (_), dollar sign ($)
                *       and pound sign (#)
                * so we need to clean out all the above from the newpassword
                */

               // This takes out any numeric characters from the start of the string
               String temp = null;
               while (true) {
                   temp = newpassword.substring(0,1);
                   if (temp.matches("[0-9]")) {
                       String s = newpassword.substring(1);
                       newpassword = s;
                   } else
                       break;
               }

               // This ensures that there are only alphanumeric characters in the string
               int nplen = newpassword.length();
               StringBuilder sb = new StringBuilder();
               for (int index = 0; index < nplen; index++) {
                   if (newpassword.substring(index, index+1).matches("[a-zA-Z0-9]"))
                           sb.append(newpassword.substring(index, index+1));
               }
               newpassword = sb.toString();

               // This limits the new password to 30 characters in length
               if (newpassword.length() > 30) {
                   String ora30charpassword = newpassword.substring(0, 30);
                   newpassword = ora30charpassword;
               }

               // We're finally ready to change the password (stupid Oracle!!)
               stmt.execute("ALTER USER " + username + " IDENTIFIED BY " + newpassword);
               con.close(); // Close the old connection

               // Open and close a connection with the new password
               // If this doesn't throw an exception then we're OK
               con = DriverManager.getConnection(url, username, newpassword);
               con.close();
           }

       } catch (ClassNotFoundException e) {
           System.err.println("\nERROR: " + e.getLocalizedMessage() + '\n');
           e.printStackTrace();
           return false;
       } catch (SQLException e) {
           System.err.println("\nERROR: " + e.getLocalizedMessage() + '\n');
           e.printStackTrace();
           return false;
       }

       System.out.println("Database password was changed successfully");
       return true;

   }

   /**
    *****************************************
    *  CREATE KEK PARAMS
    *****************************************
    * Creates a file that contains properties necessary for creating
    * the KeyEncryptingKey (KEK).  The properties are written out to
    * a file whose name is the current time-stamp with a .kek extension.
    *
    * @param    keklocation Directory where the KEK properties file
    * will be written out; only directory name ending with a trailing
    * "/" or "\" is required - filename is not required.
    * @param    dbplocation Location of the db.properties file
    *
    * @return String keycode of the new KEK file if the method succeeded,
    * or null otherwise
    *
    * @throws InvalidLocationException if location is zero-length
    * @throws InvalidEnvironmentException if this is not PRODUCTION
    * @throws OtherKeyMgtException for all other errors
    * @throws NonexistentJDBCParameterException
    */
   public static String createKEKParams(String keklocation, String dbplocation)
       throws InvalidLocationException, InvalidEnvironmentException,
       OtherKeyMgtException, NonexistentJDBCParameterException {

       // Is this production?
       String environment = System.getProperty(MARKETLIVE_ENVIRONMENT_VARIABLE_NAME, "null");
       if (environment.equals(MARKETLIVE_PRODUCTION_ENVIRONMENT_NAME))
           throw new InvalidEnvironmentException("Cannot change a PRODUCTION environment KEK.");

       // Is location zero-length?
       if (keklocation.length() == 0)
           throw new InvalidLocationException("KEK properties location cannot be zero-length");
       else if (!(keklocation.substring(keklocation.length() - 1, keklocation.length()).equals(System.getProperty("file.separator"))))
           throw new InvalidLocationException("KEK properties location must be a directory " +
                "and end with a " + System.getProperty("file.separator"));

       // Is database password file location zero-length?
       if (dbplocation.length() == 0)
           throw new InvalidLocationException("Database properties location cannot be zero-length");

       // Create properties object
       Properties props = new Properties();

       // Set keycode
       SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
       String keycode = formatter.format(new Date());
       props.setProperty("keycode", keycode);

       // Set salt for PBE - seconds since epoch - Jan 01, 1970 00:00:00
       String salt = Long.toString(new Date().getTime());
       props.setProperty("salt", salt);

       // Set number of iterations for PBE
       props.setProperty("iterations", Integer.toString(ITERATIONS));

       // Set key-rotation indicator
       props.setProperty("rotate", "N");

       // Instantiate a KeystoreManager
       KeystoreManager km = new KeystoreManager();

       try {
           // Save KEK params
           String kekfile = keycode + ".kek";
           System.out.println("Full filename: " + keklocation + kekfile);
           props.store(new FileOutputStream(keklocation + kekfile), null);

           // Generate a PBE key and encrypt some data for testing
           SecretKey pbekey = km.generatePBEKey(keycode);

//           // This is a temporary test to verify that PBE works
//           byte[] input = "This is a test for PBE".getBytes("UTF-8");
//           System.out.println("Plaintext: This is a test for PBE");
//           String ciphertext = new CryptoManager().encrypt(input, pbekey, salt, ITERATIONS);
//           System.out.println("Ciphertext: " + ciphertext);
//           FileOutputStream fos = new FileOutputStream("/tmp/pbe.ct");
//           fos.write(ciphertext.getBytes("UTF-8"));
//           fos.close();
//           // End of temporary test

           // Generate key-pair
           KeyPair kp = km.generateRSAKey(RSA_KEY_SIZE);

           // Get raw public and private keys
           byte[] pbk = kp.getPublic().getEncoded();
           PrivateKey pvk = kp.getPrivate();

           // Encrypt PrivateKey with PBE key; just Base64-encode PublicKey
           CryptoManager cm = new CryptoManager();
           String b64pbk = new String(Base64.encodeBase64(pbk));
           String b64pvkct = cm.wrap(pvk, pbekey, salt, ITERATIONS);

           // Get JDBC parameters
           DBPropertiesBean dbpb = getDBProperties(dbplocation);

           // Reset all existing keys to inactive status in DB; this is
           // mostly necessary for test-data keys that may not have been
           // cleared out of the database
           km.setAllKeysInactive(dbpb);

           // Save them to DB
           km.saveKeysToDB(keycode, b64pbk, b64pvkct, dbpb);

           // Temporary test to generate customer passwords
//           System.out.println("Base64, encrypted Public key: " + b64pbkct);
//           System.out.println("Base64, encrypted Private key: " + b64pvkct);
//           fos = new FileOutputStream("/tmp/" + keycode + "-pbk.b64");
//           fos.write(b64pbk.getBytes("UTF-8"));
//           fos.close();
//           System.out.println("Wrote out public key");
//           fos = new FileOutputStream("/tmp/" + keycode + "-pvk.ct");
//           fos.write(b64pvkct.getBytes("UTF-8"));
//           fos.close();
//           System.out.println("Wrote out private key");

           // *********** Temporary test to generate customer passwords **********
//           javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("RSA/ECB/NoPadding");
//           c.init(javax.crypto.Cipher.ENCRYPT_MODE, kp.getPublic(), new SecureRandom());
//           byte[] pt = "abcd1234".getBytes("utf-8");
//           byte[] ctbytes = new byte[c.getOutputSize(pt.length)];
//           System.out.println("RSA encrypting...");
//           int ctlen = c.update(pt, 0, pt.length, ctbytes, 0);
//           ctlen = c.doFinal(ctbytes, ctlen);
//           byte[] b64ct = Base64.encodeBase64(ctbytes);
//           FileOutputStream fos = new FileOutputStream("/tmp/rsaenc.ct");
//           fos.write(b64ct);
//           fos.close();
//           System.out.println("Wrote out RSA ciphertext");
           // ************ End of temporary test ***********

           return keycode;

       } catch (IOException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An IOException was thrown; check logs");
       } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NoSuchAlgorithmException was thrown; check logs");
       } catch (InvalidKeySpecException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidKeySpecException was thrown; check logs");
       } catch (InvalidKeyException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidKeyException was thrown; check logs");
       } catch (NoSuchPaddingException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NoSuchPaddingException was thrown; check logs");
       } catch (IllegalBlockSizeException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An IllegalBlockSizeException was thrown; check logs");
//       } catch (BadPaddingException e) {
//           e.printStackTrace();
//           throw new OtherKeyMgtException("An BadPaddingException was thrown; check logs");
       } catch (InvalidAlgorithmParameterException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidAlgorithmParameterException was thrown; check logs");
//       } catch (ShortBufferException e) {
//             // TODO Auto-generated catch block
//             e.printStackTrace();
       }
   }

   /**
    *****************************************
    *  MIGRATE KEK FROM 5.3.x TO 5.4.x
    *****************************************
    * Migrates the 5.3.0.21 KEK file to a 5.4.x-type KEK parameters
    * file.  It generates a new RSA key-pair and replaces the existing
    * key-pair with a new one.  It also generates a new Password-Based-
    * Encryption (PBE) symmetric key to encrypt the RSA Privatekey.
    * Finally, it decrypts all currently encrypted data (with the old
    * key) and re-encrypts it with the newly generated RSA PublicKey.
    *
    * @param    oldkeklocation where the old KEK file exists; the new
    * KEK parameters file will be written out to the same directory as the
    * old file while the old file will be renamed to *.53x
    * @param    dbplocation location of the db.properties file
    *
    * @return String newkeycode if the method succeeded, or null otherwise
    *
    * @throws InvalidLocationException if location is zero-length
    * @throws InvalidEnvironmentException if this is not PRODUCTION
    * @throws NonexistentJDBCParameterException if a JDBC param is missing
    * @throws OtherKeyMgtException for all other errors
    */
   public static String migrateKEK(String oldkeklocation, String dbplocation)
       throws InvalidLocationException, InvalidEnvironmentException,
       FileNotFoundException, NonexistentJDBCParameterException,
       OtherKeyMgtException {

       // Is location zero-length?
       if (oldkeklocation.length() == 0)
           throw new InvalidLocationException("KEK properties location cannot be zero-length");
       else if (!oldkeklocation.endsWith(".kek"))
           throw new InvalidLocationException("KEK properties filename must end with a \".kek\"");
       else if (dbplocation.length() == 0)
           throw new InvalidLocationException("DB properties location cannot be zero-length");

       // Does old KEK file exist?
       File kekfile = new File(oldkeklocation);
       if (!kekfile.exists())
           throw new FileNotFoundException(
                   "KEK parameter file does NOT exist at this location: " + kekfile.getAbsolutePath());

       // Is old KEK file greater than 34 bytes?
       if (kekfile.length() > 34L)
           throw new OtherKeyMgtException(
                   "KEK file appears to be a 5.4.x parameter file and cannot be migrated: " + kekfile.getAbsolutePath());

       // Does db.properrties file exist?
       File dbpfile = new File(dbplocation);
       if (!dbpfile.exists())
           throw new FileNotFoundException(
                   "DB parameter file does NOT exist at this location: " + dbpfile.getAbsolutePath());

       // Read in KEK
       SecretKey tripleDesKey = null;
       String kekString = null;
       byte [] kekArray =  null;

       try {
           BufferedReader br = new BufferedReader(new FileReader(kekfile));
           kekString = br.readLine();
           kekArray =  Base64.decodeBase64(kekString.getBytes());
           tripleDesKey = new SecretKeySpec(kekArray, "DESede");
           br.close();

           // Create a CryptoManager instance
           CryptoManager cm = new CryptoManager();

           // Create a KeystoreManager instance
           KeystoreManager km = new KeystoreManager();

           // Get DBPropertiesBean
           DBPropertiesBean dbpb = getDBProperties(dbplocation);

           // Get directory name of KEK file
           String kekdirectory = kekfile.getParent() + System.getProperty("file.separator");
           System.out.println("KEK file directory: " + kekdirectory);

           // Get old keycode - pulls out 20050426161259 from something like
           // C:\marketlive\sites\PCISite\trunk\deploy\ml5-src-app\conf\default\system\20050426161259.kek
           String filename = kekfile.getName();
           String oldkeycode = filename.substring(0, filename.lastIndexOf(".kek"));

           // Update status of old key to inactive first
           if (!km.setKeyInactive(oldkeycode, dbpb))
               throw new OtherKeyMgtException("ERROR: Could not set 5.3.x key to Inactive status; check logs");

           // Create 5.4.x-style KEK parameters file
           String newkeycode = createKEKParams(kekdirectory, dbplocation);
           if (newkeycode == null)
               throw new OtherKeyMgtException("ERROR: Could not generate 5.4.x KEK parameters; check logs");

           // Extract existing RSA PrivateKey from DB
           String b64pvkct = km.getKey(oldkeycode, dbpb, "private");

           // Decrypt existing RSA PrivateKey
           RSAPrivateKey oldpvk = cm.unwrap53x(b64pvkct.getBytes("utf-8"), tripleDesKey);

           // Extract new RSA PublicKey (Base64-encoded key) from DB
           String b64pbk = km.getKey(newkeycode, dbpb, "public");
           X509EncodedKeySpec eks = new X509EncodedKeySpec(Base64.decodeBase64(b64pbk.getBytes("utf-8")));
           KeyFactory kf = KeyFactory.getInstance("RSA");
           PublicKey newpbk = kf.generatePublic(eks);

           // Access all Admin user passwords, decrypt them with existing
           // private-key and encrypt it with new public key
           if (cm.reencryptAdminPasswords(oldkeycode, oldpvk, newkeycode, newpbk, dbpb) == 0)
               System.out.println("No Admin passwords were re-encrypted; none were found encrypted with the old key");

           // Access all customer passwords, decrypt them with existing
           // private-key and encrypt it with new public key
           if (cm.reencryptCustomerPasswords(oldkeycode, oldpvk, newkeycode, newpbk, dbpb) == 0)
               System.out.println("No customer passwords were re-encrypted; none were found encrypted with the old key");

           // Access all order payment credit card numbers, decrypt them with
           // existing private-key and encrypt it with new public key
           if (cm.reencryptCreditCardNumbers(oldkeycode, oldpvk, newkeycode, newpbk, dbpb) == 0)
               System.out.println("No customer CCN were re-encrypted; none were found encrypted with the old key");

           // Rename old kek parameter file
           kekfile.renameTo(new File(kekfile.getAbsolutePath() + ".53x"));

           return newkeycode;

       } catch (IOException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An IOException was thrown; check logs");
       } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NoSuchAlgorithmException was thrown; check logs");
       } catch (InvalidKeySpecException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidKeySpecException was thrown; check logs");
       } catch (InvalidKeyException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidKeyException was thrown; check logs");
       } catch (NoSuchPaddingException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NoSuchPaddingException was thrown; check logs");
       } catch (NumberFormatException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NumberFormatException was thrown; check logs");
       } catch (InvalidAlgorithmParameterException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidAlgorithmParameterException was thrown; check logs");
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An ClassNotFoundException was thrown; check logs");
       } catch (SQLException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An SQLException was thrown; check logs");
       }
   }


   /**
    *****************************************
    *  ROTATE KEY
    *****************************************
    * Rotates the RSA public/private key-pair in the database; i.e.
    * it replaces the existing key-pair with a new one.  However, it
    * also decrypts all currently encrypted data (with the old key)
    * and re-encrypts it with the newly generated key.
    *
    * @param    keklocation Directory where a the KEK properties file
    * will be written out;
    * @param    dbplocation Location of the db.properties file
    *
    * @return String newkeycode if the method succeeded, or null otherwise
    *
    * @throws InvalidLocationException if location is zero-length
    * @throws InvalidEnvironmentException if this is not PRODUCTION
    * @throws NonexistentJDBCParameterException if a JDBC param is missing
    * @throws NonexistentKEKParameterException if a KEK param is missing
    * @throws OtherKeyMgtException for all other errors
    */
   public static String rotateKey(String keklocation, String dbplocation)
       throws InvalidLocationException, InvalidEnvironmentException,
       FileNotFoundException, NonexistentJDBCParameterException,
       NonexistentKEKParameterException, OtherKeyMgtException {

       // Is this production?
       String environment = System.getProperty(MARKETLIVE_ENVIRONMENT_VARIABLE_NAME, "null");
       if (environment.equals(MARKETLIVE_PRODUCTION_ENVIRONMENT_NAME))
           throw new InvalidEnvironmentException("Cannot change a PRODUCTION environment KEK.");

       // Is location zero-length?
       if (keklocation.length() == 0)
           throw new InvalidLocationException("KEK properties location cannot be zero-length");
       else if (!keklocation.endsWith(".kek"))
           throw new InvalidLocationException("KEK properties filename must end with a \".kek\"");
       else if (dbplocation.length() == 0)
           throw new InvalidLocationException("DB properties location cannot be zero-length");

       // Does KEK file exist?
       File kekfile = new File(keklocation);
       if (!kekfile.exists())
           throw new FileNotFoundException(
                   "KEK parameter file does NOT exist at this location: " + kekfile.getAbsolutePath());

       // Does db.properrties file exist?
       File dbpfile = new File(dbplocation);
       if (!dbpfile.exists())
           throw new FileNotFoundException(
                   "DB parameter file does NOT exist at this location: " + dbpfile.getAbsolutePath());

       // Read in KEK properties
       KEKPropertiesBean kekpb = getKEKProperties(keklocation);

       // Get db.properties
       DBPropertiesBean dbpb = getDBProperties(dbplocation);

       // Instantiate a KeystoreManager
       KeystoreManager km = new KeystoreManager();

       try {

           // Check if rotate does not say Y
           if (!"Y".equals(kekpb.getRotate()))
               throw new OtherKeyMgtException("KEK properties does not indicate that keys must be rotated");

           // Generate the PBE key and encrypt some data for testing
           SecretKey oldpbekey = new KeystoreManager().generatePBEKey(kekpb.getKeycode());

//           // ************  Temporary test to see if PBE works *********************
//           File ctfile = new File ("/tmp/pbe.ct");
//           byte[] ciphertext = new byte[(int)ctfile.length()];
//           FileInputStream fis = new FileInputStream("/tmp/pbe.ct");
//           fis.read(ciphertext);
//           System.out.println("Ciphertext: " + new String(ciphertext, "utf-8"));
//           System.out.println("Plaintext: " +
//                   new CryptoManager().decrypt(ciphertext, oldpbekey, salt,
//                   iterations));

//           ctfile = new File("/tmp/" + keycode + "-pvk.ct");
//           ciphertext = new byte[(int)ctfile.length()];
//           fis = new FileInputStream("/tmp/" + keycode + "-pvk.ct");
//           fis.read(ciphertext);
//           System.out.println("Ciphertext: " + new String(ciphertext, "utf-8"));
//           PrivateKey pvk = (PrivateKey) cm.unwrap(ciphertext, pbekey, salt, iterations);
//
//           javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("RSA/ECB/NoPadding");
//           c.init(javax.crypto.Cipher.DECRYPT_MODE, pvk, new SecureRandom());
//           ctfile = new File("/tmp/rsaenc.ct");
//           ciphertext = new byte[(int)ctfile.length()];
//           fis = new FileInputStream("/tmp/rsaenc.ct");
//           fis.read(ciphertext);
//           System.out.println("Ciphertext: " + new String(ciphertext, "utf-8"));
//
//           byte[] ctbytes = org.apache.commons.codec.binary.Base64.decodeBase64(ciphertext);
//
//           byte[] ptbytes = new byte[c.getOutputSize(ctbytes.length)];
//           System.out.println("RSA decrypting...");
//           int ptlen = c.update(ctbytes, 0, ctbytes.length, ptbytes, 0);
//           ptlen = c.doFinal(ptbytes, ptlen);
//           System.out.println("Plaintext: " + new String(ptbytes));
           // **********************  End of temporary test ****************************

           // Create a CryptoManager instance
           CryptoManager cm = new CryptoManager();

           // Create new properties
           Properties newkekprops = new Properties();

           // Get and set new keycode
           SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
           String newkeycode = formatter.format(new Date());
           newkekprops.setProperty("keycode", newkeycode);
           if (newkeycode.equals(kekpb.getKeycode()))
               throw new OtherKeyMgtException("ERROR: The old and new keycodes are identical; cannot continue");

           // Set salt for PBE - seconds since epoch - Jan 01, 1970 00:00:00
           String newsalt = Long.toString(new Date().getTime());
           newkekprops.setProperty("salt", newsalt);

           // Set number of iterations for PBE
           newkekprops.setProperty("iterations", Integer.toString(ITERATIONS));

           // Set rotation flag
           newkekprops.setProperty("rotate", "N");

           // Generate a new RSA key-pair
           KeyPair newkp = km.generateRSAKey(RSA_KEY_SIZE);

           // Get raw public and private keys
           PrivateKey newpvk = newkp.getPrivate();
           PublicKey newpbk = newkp.getPublic();
           byte[] newpbkbytes = newpbk.getEncoded();

           // Generate the new PBE key because the keycode changed
           SecretKey newpbekey = new KeystoreManager().generatePBEKey(newkeycode);

           // Encrypt PrivateKey with PBE key; just Base64-encode PublicKey
           String newb64pbk = new String (Base64.encodeBase64(newpbkbytes));
           String newb64pvkct = cm.wrap(newpvk, newpbekey, newsalt, ITERATIONS);

           // Update status of old key to inactive
           if (!km.setKeyInactive(kekpb.getKeycode(), dbpb))
               throw new OtherKeyMgtException("ERROR: Could not set current key to Inactive status; check logs");

           /*
            * Save new key-pair in DB with new keycode; if it cannot
            * be saved, reset the old key to active status (because the
            * save option sets the new keycode to active status)
            */
           if (!km.saveKeysToDB(newkeycode, newb64pbk, newb64pvkct, dbpb)) {
               km.setKeyActive(kekpb.getKeycode(), dbpb);
               throw new OtherKeyMgtException("ERROR: Could not save new key to Database; check logs");
           }

           // Extract existing RSA key-pair from DB
           String b64pvkct = km.getKey(kekpb.getKeycode(), dbpb, "private");

           // Decrypt existing RSA PrivateKey
           PrivateKey oldpvk = (PrivateKey) cm.unwrap(b64pvkct.getBytes("utf-8"), oldpbekey, kekpb.getSalt(), kekpb.getIterations());

           // Access all Admin passwords, decrypt them with existing
           // private-key and encrypt it with new public key.
           if (cm.reencryptAdminPasswords(kekpb.getKeycode(), oldpvk, newkeycode, newpbk, dbpb) == 0)
               System.out.println("No Admin passwords were re-encrypted; none were found encrypted with the old key");

           // Access all customer passwords, decrypt them with existing
           // private-key and encrypt it with new public key.
           if (cm.reencryptCustomerPasswords(kekpb.getKeycode(), oldpvk, newkeycode, newpbk, dbpb) == 0)
               System.out.println("No customer passwords were re-encrypted; none were found encrypted with the old key");

           // Access all order payment credit card numbers, decrypt them with
           // existing private-key and encrypt it with new public key.
           if (cm.reencryptCreditCardNumbers(kekpb.getKeycode(), oldpvk, newkeycode, newpbk, dbpb) == 0)
               System.out.println("No customer CCN were re-encrypted; none were found encrypted with the old key");

           // Rename old kek parameter file
           kekfile.renameTo(new File(kekfile.getAbsolutePath() + ".old"));

           // Save new kek parameter file
           newkekprops.store(new FileOutputStream(keklocation.substring(0, keklocation.indexOf(kekpb.getKeycode())) +
                   newkeycode + ".kek"), null);

           return newkeycode;

//           // ************ TESTING ************
//           // This is a temporary test to verify that PBE works
//           byte[] input = "This is a new test for PBE".getBytes("UTF-8");
//           System.out.println("Plaintext: This is a test for PBE");
//           String newciphertext = new CryptoManager().encrypt(input, newpbekey, newsalt, ITERATIONS);
//           System.out.println("Ciphertext: " + newciphertext);
//           FileOutputStream fos = new FileOutputStream("/tmp/newpbe.ct");
//           fos.write(newciphertext.getBytes("UTF-8"));
//           fos.close();
//
//           javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("RSA/ECB/NoPadding");
//           c.init(javax.crypto.Cipher.DECRYPT_MODE, oldpvk, new SecureRandom());
//           ctfile = new File("/tmp/rsaenc.ct");
//           ciphertext = new byte[(int)ctfile.length()];
//           fis = new FileInputStream("/tmp/rsaenc.ct");
//           fis.read(ciphertext);
//           System.out.println("Ciphertext: " + new String(ciphertext, "utf-8"));
//
//           byte[] ctbytes = org.apache.commons.codec.binary.Base64.decodeBase64(ciphertext);
//
//           byte[] ptbytes = new byte[c.getOutputSize(ctbytes.length)];
//           System.out.println("RSA decrypting...");
//           int ptlen = c.update(ctbytes, 0, ctbytes.length, ptbytes, 0);
//           ptlen = c.doFinal(ptbytes, ptlen);
//           System.out.println("Plaintext: " + new String(ptbytes));
//           // ************ END OF TESTING ************
//
//
//           // ************  Temporary test for RSA crypto *********************
////           System.out.println("Base64, encrypted Public key: " + newb64pbkct);
////           System.out.println("Base64, encrypted Private key: " + newb64pvkct);
//           fos = new FileOutputStream("/tmp/" + newkeycode + "-pbk.b64");
//           fos.write(newb64pbk.getBytes("UTF-8"));
//           fos.close();
//           System.out.println("Wrote out new public key");
//           fos = new FileOutputStream("/tmp/" + newkeycode + "-pvk.ct");
//           fos.write(newb64pvkct.getBytes("UTF-8"));
//           fos.close();
//           System.out.println("Wrote out new private key");
//
//           javax.crypto.Cipher newc = javax.crypto.Cipher.getInstance("RSA/ECB/NoPadding");
//           newc.init(javax.crypto.Cipher.ENCRYPT_MODE, newkp.getPublic(), new SecureRandom());
//           byte[] pt = "This is a test of RSA encryption".getBytes("utf-8");
//           byte[] newctbytes = new byte[c.getOutputSize(pt.length)];
//           System.out.println("RSA encrypting...");
//           int ctlen = newc.update(pt, 0, pt.length, newctbytes, 0);
//           ctlen = newc.doFinal(ctbytes, ctlen);
//           byte[] b64ct = org.apache.commons.codec.binary.Base64.encodeBase64(ctbytes);
//           fos = new FileOutputStream("/tmp/newrsaenc.ct");
//           fos.write(b64ct);
//           fos.close();
//           System.out.println("Wrote out new RSA ciphertext");
//           // **********************  End of temporary test ****************************

       } catch (IOException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An IOException was thrown; check logs");
       } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NoSuchAlgorithmException was thrown; check logs");
       } catch (InvalidKeySpecException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidKeySpecException was thrown; check logs");
       } catch (InvalidKeyException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidKeyException was thrown; check logs");
       } catch (NoSuchPaddingException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NoSuchPaddingException was thrown; check logs");
       } catch (IllegalBlockSizeException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An IllegalBlockSizeException was thrown; check logs");
       } catch (NumberFormatException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An NumberFormatException was thrown; check logs");
       } catch (InvalidAlgorithmParameterException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An InvalidAlgorithmParameterException was thrown; check logs");
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An ClassNotFoundException was thrown; check logs");
       } catch (SQLException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An sqlException was thrown; check logs");
       }
   }


   /**
    *****************************************
    *  GET DB PROPERTIES
    *****************************************
    * Read in db.properties file and get all properties
    *
    * @param    dbplocation location where db.properties file is located
    *
    * @return DBPropertiesBean
    *
    * @exception InvalidLocationException if location is zero-length
    * @exception NonexistentJDBCParameterException if any JDBC parameter doesn't exist
    * @exception OtherKeyMgtException for all other errors
    */
   protected static DBPropertiesBean getDBProperties(String dbplocation)
       throws InvalidLocationException, FileNotFoundException,
       NonexistentJDBCParameterException, OtherKeyMgtException {

       // Is DBP location zero-length?
       if (dbplocation.length() == 0)
           throw new InvalidLocationException("DB properties location cannot be zero-length");


       // Does db.properrties file exist?
       File f = new File(dbplocation);
       if (!f.exists())
           throw new FileNotFoundException(
                   "DB parameter file does NOT exist at this location: " + f.getAbsolutePath());

       // Read in db.properties
       Properties dbprops = new Properties();
       DBPropertiesBean dbpb = new DBPropertiesBean();

       try {
           // Load db.properties
           dbprops.load(new FileInputStream(dbplocation));

           // Check if encryptedPassword or password property is in there
           if (dbprops.containsKey("encryptedPassword"))
               dbpb.setJdbcEncryptedPassword(dbprops.getProperty("encryptedPassword"));
           else if (dbprops.containsKey("password"))
               dbpb.setJdbcPassword(dbprops.getProperty("password"));
           else
               throw new NonexistentJDBCParameterException("db.properties does not contain either the \"password\" or the \"encryptedPassword\" property in it");

           // Check if driver property is NOT in there
           if (!dbprops.containsKey("driver"))
               throw new NonexistentJDBCParameterException("db.properties does not contain the \"driver\" property in it");

           dbpb.setJdbcDriver(dbprops.getProperty("driver"));

           // Check if URL property is NOT in there
           if (!dbprops.containsKey("url"))
               throw new NonexistentJDBCParameterException("db.properties does not contain the \"url\" property in it");

           dbpb.setJdbcURL(dbprops.getProperty("url"));

           // Check if username property is NOT in there
           if (!dbprops.containsKey("username"))
               throw new NonexistentJDBCParameterException("db.properties does not contain the \"username\" property in it");

           dbpb.setJdbcUsername(dbprops.getProperty("username"));

       } catch (IOException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An IOException was thrown; check logs");
       }
       return dbpb;
   }

   /**
    *****************************************
    *  GET KEK PROPERTIES
    *****************************************
    * Read in the KEK properties file and get properties defined
    * in there
    *
    * @param    keklocation location where the KEK file is located;
    * filename IS required.
    *
    * @return KEKPropertiesBean
    *
    * @exception InvalidLocationException if location is zero-length
    * @exception FileNotFoundException if KEK file cannot be found
    * @exception NonexistentKEKParameterException if a KEK parameter doesn't exist
    * @exception OtherKeyMgtException for all other errors
    */
   protected static KEKPropertiesBean getKEKProperties(String keklocation)
       throws InvalidLocationException, FileNotFoundException,
       NonexistentKEKParameterException, OtherKeyMgtException {

       // Is KEK file location zero-length?
       if (keklocation.length() == 0)
           throw new InvalidLocationException("KEK properties location cannot be zero-length");

       // Does KEK file exist?
       File f = new File(keklocation);
       if (!f.exists())
           throw new FileNotFoundException(
                   "KEK parameter file does NOT exist at this location: " + f.getAbsolutePath());

       // Read in db.properties
       Properties kekprops = new Properties();
       KEKPropertiesBean kekp = new KEKPropertiesBean();

       try {

           // Load and check KEK properties
           kekprops.load(new FileInputStream(keklocation));

           // Check if keycode property is NOT in there
           if (!kekprops.containsKey("keycode"))
               throw new NonexistentKEKParameterException("KEK properties does not contain the \"keycode\" property in it");

           kekp.setKeycode(kekprops.getProperty("keycode"));

           // Check if salt property is NOT in there
           if (!kekprops.containsKey("salt"))
               throw new NonexistentKEKParameterException("KEK properties does not contain the \"salt\" property in it");

           kekp.setSalt(kekprops.getProperty("salt"));

           // Check if iterations property is NOT in there
           if (!kekprops.containsKey("iterations"))
               throw new NonexistentKEKParameterException("KEK properties does not contain the \"iterations\" property in it");

           kekp.setIterations(Short.parseShort(kekprops.getProperty("iterations")));

           // Check if rotate property is NOT in there
           if (!kekprops.containsKey("rotate"))
               throw new NonexistentKEKParameterException("KEK properties does not contain the \"rotate\" property in it");

           kekp.setRotate(kekprops.getProperty("rotate"));

       } catch (IOException e) {
           e.printStackTrace();
           throw new OtherKeyMgtException("An IOException was thrown; check logs");
       }
       return kekp;
   }

   public static void changeKeystorePassword() {
       // TODO Auto-generated method stub
   }

   /*
    *****************************************
    *  GET JDBC DATABASE CONNECTION
    *****************************************
    * Gets a database connection based on whether the password
    * passed in is a plaintext or encrypted password
    *
    * @param DBPropertiesBean containing JDBC properties
    * @return java.sql.Connection
    * @throws ClassNotFoundException
    * @throws SQLException
    * @throws NoSuchAlgorithmException
    * @throws UnsupportedEncodingException
    */
   public static Connection getDBConnection(DBPropertiesBean dbpb)
       throws ClassNotFoundException, SQLException,
       NoSuchAlgorithmException, UnsupportedEncodingException {

       String dbpassword = null;       // Plaintext DB password
       Connection con = null;          // JDBC Connection

       // Load JDBC driver
       Class.forName(dbpb.getJdbcDriver());

       // Check if password properrty is not null
       if (dbpb.getJdbcPassword() != null)
           con = DriverManager.getConnection(
               dbpb.getJdbcURL(), dbpb.getJdbcUsername(), dbpb.getJdbcPassword());
       else {
           // Check if encryptedPassword properrty is not null
           if (dbpb.getJdbcEncryptedPassword() != null) {

                // This works for SQL Server
                dbpassword = new CryptoManager()
                   .hash(dbpb.getJdbcEncryptedPassword()
                   .getBytes("utf-8"), HASH_TYPE);

               /*
                * However, Oracle has a lot of constraints on passwords:
                *  - it cannot be greater than 30 chars in length
                *  - it must not begin with a number
                *  - it cannot contain quotation marks
                *  - it can contain only underscores (_), dollar sign ($)
                *       and pound sign (#)
                * so we need to clean out all the above from dbpassword
                */
               if (dbpb.getJdbcURL().indexOf("oracle") > 0) {

		   // First copy the hashed string
                   String encpassword = new String(dbpassword);

                   // Now take out any numeric characters from the start of the string
                   String temp = null;
                   while (true) {
                       temp = encpassword.substring(0,1);
                       if (temp.matches("[0-9]")) {
                           String s = encpassword.substring(1);
                           encpassword = s;
                       } else
                           break;
                   }

                   // This ensures that there are only alphanumeric characters in the string
                   int nplen = encpassword.length();
                   StringBuilder sb = new StringBuilder();
                   for (int index = 0; index < nplen; index++) {
                       if (encpassword.substring(index, index+1).matches("[a-zA-Z0-9]"))
                               sb.append(encpassword.substring(index, index+1));
                   }
                   encpassword = sb.toString();

                   // This limits the new password to 30 characters in length
                   if (encpassword.length() > 30) 
                       dbpassword = encpassword.substring(0, 30);
                   else 
		       dbpassword = encpassword;
               } 

               // Now create a connection
               con = DriverManager.getConnection(
                       dbpb.getJdbcURL(), dbpb.getJdbcUsername(), dbpassword);
           }
       }
       // Connection will be null if either password or encryptedPassword
       // does not exist in DBPropertiesbean
       return con;
   }
}
